package com.example.lottery.service.mq;

import cn.hutool.core.date.DateUtil;
import com.example.lottery.common.config.DirectRabbitConfig;
import com.example.lottery.common.errorcode.ServiceErrorCodeConstants;
import com.example.lottery.common.exception.ServiceException;
import com.example.lottery.common.utils.JacksonUtil;
import com.example.lottery.common.utils.MailUtil;
import com.example.lottery.common.utils.RedisUtil;
import com.example.lottery.controller.param.DrawPrizeParam;
import com.example.lottery.dao.dataobject.ActivityPrizeDO;
import com.example.lottery.dao.dataobject.WinningRecordDO;
import com.example.lottery.dao.mapper.ActivityPrizeMapper;
import com.example.lottery.dao.mapper.WinningRecordMapper;
import com.example.lottery.service.IDrawPrizeService;
import com.example.lottery.service.activitystatus.IActivityStatusManger;
import com.example.lottery.service.dto.ConvertActivityStatusDTO;
import com.example.lottery.service.enums.ActivityEnum;
import com.example.lottery.service.enums.ActivityPrizeEnum;
import com.example.lottery.service.enums.ActivityUserEnum;
import com.example.lottery.service.enums.PrizeTiersEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@Component
@RabbitListener(queues = DirectRabbitConfig.QUEUE_NAME)
public class DrawMqReceiver {
    @Autowired
    private IDrawPrizeService drawPrizeService;
    @Autowired
    private IActivityStatusManger activityStatusManger;
    @Autowired
    private MailUtil mailUtil;
    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;
    @Autowired
    private WinningRecordMapper winningRecordMapper;
    @Autowired
    private RedisUtil redisUtil;

    @RabbitHandler
    public void process(Map<String,String> messages){
        log.info("process message:{}", JacksonUtil.writeValueAsString(messages));
        //取出消息
        String messageData = messages.get("messageData");
        DrawPrizeParam drawPrizeParam = JacksonUtil.readValue(messageData, DrawPrizeParam.class);

        try{
            //  核对抽奖信息有效性
            if(drawPrizeService.checkDrawPrizeParam(drawPrizeParam)==false){
                return;
            }
            //  扭转状态
            convertStatus(drawPrizeParam);
            //  记录中奖信息
            List<WinningRecordDO> winningRecordDOS = drawPrizeService.recordWinningInfo(drawPrizeParam);

            //  通知中奖者
            notifyWinner(winningRecordDOS);
        }catch (ServiceException e){
            log.warn("处理MQ消息异常 error code:{},msg:{}",e.getCode(),e.getMsg(),e);
            //保证事物一致性：回滚操作
            rollback(drawPrizeParam);
            //抛异常 消息重发
            throw e;
        }catch (Exception e){
            log.warn("处理MQ消息异常 ",e);
            //保证事物一致性：回滚操作
            rollback(drawPrizeParam);
            //抛异常 消息重发
            throw e;
        }
    }

    /**
     * 回滚操作
     * @param drawPrizeParam
     */
    //因为捕获到了异常，只要修改了数据一定要进行回滚！
    private void rollback(DrawPrizeParam drawPrizeParam) {
        //1-    判断状态是否发生扭转 添加了@Trancational
        if(!isConvertStatus(drawPrizeParam)){
            //状态没有扭转、不需要回滚，说明是在校验用户信息抛出的异常，直接return
            return;
        }
        //状态扭转，进行回滚，一定是后面的步骤抛出的异常，继续往下判断，不能return
        rollbackStatus(drawPrizeParam);

        //2-    判断是否记录中奖信息 没有添加@Trancational
        if(!isRecordDrawPrize(drawPrizeParam)){
            //没有记录，不需要回滚
            return;
        }
        //记录了，需要回滚
        rollbackDrawPrize(drawPrizeParam);
    }

    /**
     * 扭转中奖信息
     * @param drawPrizeParam
     */
    private void rollbackDrawPrize(DrawPrizeParam drawPrizeParam) {
        drawPrizeService.deleteRecords(drawPrizeParam.getActivityId(),drawPrizeParam.getPrizeId());
    }

    /**
     * 判断是否记录中奖信息
     * @param drawPrizeParam
     * @return
     */
    private boolean isRecordDrawPrize(DrawPrizeParam drawPrizeParam) {
        List<WinningRecordDO> winningRecordDOS = winningRecordMapper.selectByPrimaryKeys(drawPrizeParam.getActivityId(),
                drawPrizeParam.getPrizeId(),
                drawPrizeParam.getWinnerList().stream().map(winner -> {
                    return winner.getUserId();
                }).collect(Collectors.toList()));

        return winningRecordDOS.size()>0;
    }

    /**
     * 扭转状态 从COMPLETE->INIT
     * @param drawPrizeParam
     */
    private void rollbackStatus(DrawPrizeParam drawPrizeParam) {
        ConvertActivityStatusDTO convertActivityStatusDTO=new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setPrizeId(drawPrizeParam.getPrizeId());
        //目标状态
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeEnum.INIT);

        convertActivityStatusDTO.setUserIds(drawPrizeParam.getWinnerList().stream().
                map(winner -> {
                    Long userId= winner.getUserId();
                    return userId;
                }).collect(Collectors.toList()));
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserEnum.INIT);


        convertActivityStatusDTO.setActivityId(drawPrizeParam.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityEnum.RUNNING);

        activityStatusManger.rollbackConvertHandler(convertActivityStatusDTO);
    }

    /**
     * 判断状态是否扭转
     * @param drawPrizeParam
     * @return
     */
    private boolean isConvertStatus(DrawPrizeParam drawPrizeParam) {
        //1-    奖品状态是否为COMPLETE
        ActivityPrizeDO activityPrizeDO = activityPrizeMapper.selectPAInfo(drawPrizeParam.getPrizeId(), drawPrizeParam.getActivityId());
        if(activityPrizeDO.getStatus().equalsIgnoreCase(ActivityPrizeEnum.COMPLETE.name())){
            return true;
        }
        return false;
    }



    /**
     * 通知中奖者
     * @param winningRecordDOS
     */
    public void notifyWinner(List<WinningRecordDO> winningRecordDOS) {
        if(winningRecordDOS.isEmpty()){
            log.warn("中奖信息为空");
            throw new ServiceException(ServiceErrorCodeConstants.NOTIFY_WINNER_PARAM_NULL);
        }
        winningRecordDOS.forEach(winningRecordDO -> {
           if(!mailUtil.sendSampleMail(winningRecordDO.getWinnerEmail(),
                    "中奖通知",
                    "Hi," + winningRecordDO.getWinnerName() + "。\n" +
                            "恭喜你在" + winningRecordDO.getActivityName() + "活动中获得"
                            + PrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMsg() + "："
                            +"< "+winningRecordDO.getPrizeName()+" >" +"\n"+
                            "获奖时间为：" + DateUtil.formatTime(winningRecordDO.getWinningTime()) + ",请尽快领取您的奖励！")) {
               log.warn("发送邮件给 winnerId:{},winnerName:{} activityId:{},prizeId:{} 失败",
                       winningRecordDO.getWinnerId(), winningRecordDO.getWinnerName(),
                       winningRecordDO.getActivityId(), winningRecordDO.getPrizeId());
           }
        });
    }


    /**
     * 状态扭转
     * @param drawPrizeParam 含活动ID、奖品ID、用户ID
     */
    private void convertStatus(DrawPrizeParam drawPrizeParam) {
        ConvertActivityStatusDTO convertActivityStatusDTO=new ConvertActivityStatusDTO();

        convertActivityStatusDTO.setPrizeId(drawPrizeParam.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeEnum.COMPLETE);

        convertActivityStatusDTO.setUserIds(drawPrizeParam.getWinnerList().stream().map(winner -> {
            Long userId= winner.getUserId();
            return userId;
        }).collect(Collectors.toList()));
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserEnum.COMPLETE);

        convertActivityStatusDTO.setActivityId(drawPrizeParam.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityEnum.COMPLETE);

        activityStatusManger.convertHandler(convertActivityStatusDTO);

        //1-    奖品扭转
        //查询活动奖品信息
        //扭转条件：奖品状态为INIT


        //2-    人员扭转
        //查询活动人员信息
        //扭转条件：人员状态为INIT

        //3-    活动扭转
        //查询活动信息
        //扭转条件：奖品状态为RUNNING，并且圈选的奖品的状态都为COMPLETE才去扭转活动状态


        //4-    更新redis缓存中的信息
    }
}
